package com.nulldev.util.web.URLUtils;

import java.net.URI;
import java.net.URL;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.VariableAPI.util.strings.splitters.Splitters;
import com.nulldev.util.codecs.data.impl.URLDecoder;
import com.nulldev.util.codecs.data.impl.URLEncoder;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Arrays.misc.CircularIterator;
import com.nulldev.util.data.Charsets.CharsetUtil;
import com.nulldev.util.internal.backport.concurrency9.Lists;
import com.nulldev.util.internal.backport.optionals.Optional;
import com.nulldev.util.web.legacyURI.LegacyURIUtils;

public class URLUtils {
	public static final String USER_AGENT_VERSION = "124.0.0.0";
	public static final String STANDARD_USERAGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"
			+ USER_AGENT_VERSION + " Safari/537.36";

	private static final String CHROMIUM_VERSION = USER_AGENT_VERSION.substring(0, USER_AGENT_VERSION.indexOf('.'));
	private static final boolean USE_URL_OPTS_ACCEL = Arguments.notArguments("--URLUtils.disableURLAccel");
	public static final boolean USE_URL_ACCEL_V2 = true;
	private static final Iterator<URLDecoder> DECODER_SESSIONS = new CircularIterator<URLDecoder>(
			Lists.of(new URLDecoder(), new URLDecoder(), new URLDecoder(), new URLDecoder()));
	private static final Iterator<URLEncoder> ENCODER_SESSIONS = new CircularIterator<URLEncoder>(
			Lists.of(new URLEncoder(), new URLEncoder(), new URLEncoder(), new URLEncoder()));

	public static String safeDecode(final String string) {
		try {
			return new String(DECODER_SESSIONS.next().decodeToBytes(string), CharsetUtil.UTF_8);
		} catch (RuntimeException ex) {
			return LegacyURIUtils.safeDecode(string);
		} catch (CharacterCodingException e) {
			throw new RuntimeException(e);
		}
	}

	public static String safeDecode(final String string, final String charset) {
		try {
			return new String(DECODER_SESSIONS.next().decodeToBytes(string), Charset.forName(charset));
		} catch (RuntimeException ex) {
			return LegacyURIUtils.safeDecode(string, charset);
		} catch (CharacterCodingException e) {
			throw new RuntimeException(e);
		}
	}

	public static String safeEncode(final String string) {
		try {
			return ENCODER_SESSIONS.next().encodeToString(string.getBytes(CharsetUtil.UTF_8));
		} catch (RuntimeException ex) {
			return LegacyURIUtils.safeEncode(string);
		} catch (CharacterCodingException e) {
			throw new RuntimeException(e);
		}
	}

	public static String safeEncode(final String string, final String charset) {
		try {
			return ENCODER_SESSIONS.next().encodeToString(string.getBytes(Charset.forName(charset)));
		} catch (RuntimeException ex) {
			return LegacyURIUtils.safeEncode(string, charset);
		} catch (CharacterCodingException e) {
			throw new RuntimeException(e);
		}
	}

	public static URL parseURL(final String url) {
		try {
			if (USE_URL_OPTS_ACCEL) {
				return com.nulldev.util.web.URLUtils.fparser.URL.parse(url).toFastJavaURL();
			} else {
				if (!url.startsWith("https") && !url.startsWith("http"))
					return new URL("https://" + url);
				else if (!url.startsWith("http"))
					return new URL("http://" + url);
				else
					return new URL(url);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static URL parseURL_NoAccel(final String url) {
		try {
			if (!url.startsWith("https") && !url.startsWith("http"))
				return new URL("https://" + url);
			else if (!url.startsWith("http"))
				return new URL("http://" + url);
			else
				return new URL(url);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static boolean isValid(final URL url) {
		if (url == null) {
			return false;
		}
		if (USE_URL_OPTS_ACCEL) {
			try {
				url.toURI();
				return true;
			} catch (Exception e) {
				return false;
			}
		} else {
			return isValid(url.toExternalForm());
		}
	}

	public static boolean isValid(final String url) {
		if (url == null || url.isEmpty()) {
			return false;
		}
		try {
			if (USE_URL_OPTS_ACCEL) {
				com.nulldev.util.web.URLUtils.fparser.URL.parse(url);
				return true;
			} else {
				final URL u = parseURL(url);
				u.toURI();
				return true;
			}
		} catch (Exception e) {
			return false;
		}
	}

	private static final String BLNK_STR = "";

	private static Optional<SimpleImmutableEntry<String, String>> splitKeyValue(final String text, final boolean decode) {
		final String[] v = Splitters.splitString(text, "=", true);
		if (v.length > 0) {
			try {
				final String key = decode ? safeDecode(v[0], CharsetUtil.UTF_8.name()) : v[0];
				final String value;
				if (v.length > 2) {
					final String r = text.substring(v[0].length() + 1);
					value = (decode ? safeDecode(r, CharsetUtil.UTF_8.name()) : r);
				} else if (v.length > 1) {
					value = (decode ? safeDecode(v[1], CharsetUtil.UTF_8.name()) : v[1]);
				} else {
					value = BLNK_STR;
				}
				return Optional.of(new SimpleImmutableEntry<String, String>(key, value));
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		} else {
			return Optional.empty();
		}
	}

	public static Map<String, String> parseQuery(final String query) {
		return parseQuery(query, true);
	}

	public static Map<String, String> parseQuery(final String query, final boolean decode) {
		Variables.requireNonNull(query, "query");
		final int q = (query.indexOf('?') + 1);
		final String postUrl = q > 0 ? query.substring(q) : query;
		return Splitters.byPattern("&|\\?").splitToStream(postUrl).map(o -> splitKeyValue(o, decode)).filter(Optional::isPresent).map(Optional::get)
				.reduce(new TreeMap<String, String>(), (map, kv) -> {
					map.put(kv.getKey(), kv.getValue());
					return map;
				}, (a, b) -> {
					a.putAll(b);
					return a;
				});
	}

	public static Map<String, String> parseQuery(final URL url) {
		return parseQuery(url, true);
	}

	public static Map<String, String> parseQuery(final URL url, final boolean decode) {
		return Splitters.byPattern("&|\\?").splitToStream(Variables.requireNonNullObject(url, "url").getQuery() == null ? url.getPath() : url.getQuery())
				.map(o -> splitKeyValue(o, decode)).filter(Optional::isPresent).map(Optional::get).reduce(new TreeMap<String, String>(), (map, kv) -> {
					map.put(kv.getKey(), kv.getValue());
					return map;
				}, (a, b) -> {
					a.putAll(b);
					return a;
				});
	}

	public static Map<String, String> parseQuery(final URI uri) {
		return parseQuery(uri, true);
	}

	public static Map<String, String> parseQuery(final URI uri, final boolean decode) {
		return Splitters.byPattern("&|\\?").splitToStream(Variables.requireNonNullObject(uri, "uri").getQuery()).map(o -> splitKeyValue(o, decode))
				.filter(Optional::isPresent).map(Optional::get).reduce(new TreeMap<String, String>(), (map, kv) -> {
					map.put(kv.getKey(), kv.getValue());
					return map;
				}, (a, b) -> {
					a.putAll(b);
					return a;
				});
	}

	public static String getDomain(final String url) {
		try {
			final com.nulldev.util.web.URLUtils.fparser.URL u = com.nulldev.util.web.URLUtils.fparser.URL.parse(url);
			return u.getHost();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static String makeChromiumUAHeader() {
		return "\"Chromium\";v=\"" + CHROMIUM_VERSION + "\", \"Not;A=Brand\";v=\"8\"";
	}
}
